﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CRFEncoder
{
    class FeatureIndex
    {
        public List<int []> feature_cache_;
        public float cost_factor_;
        public BigArray<float> alpha_;
        public int maxid_;
        public int thread_num_;
        public List<string> unigram_templs_;
        public List<string> bigram_templs_;
        public List<string> y_;
        public int        xsize_;
        public Dictionary<string, FeatureIdPair> dic_ = new Dictionary<string, FeatureIdPair>();


        public void set_alpha(ref BigArray<float> alpha) { alpha_ = alpha; }
        public string y(int i){ return y_[i]; }
        public int xsize() { return xsize_; }
        public int size() { return maxid_;}

        public FeatureIndex()
        {
            thread_num_ = 1;
            maxid_ = 0;
            cost_factor_ = 1.0f;
            feature_cache_ = new List<int[]>();
        }

        public void calcCost(int m, int n, ref Node[,] node)
        {
            float c = 0;
            int[] f = feature_cache_[node[m, n].fid];
            for (int i = 0; i < f.Length; i++)
            {
                c += (alpha_)[f[i] + n + 1];
            }
            node[m, n].cost = cost_factor_ * c;
        }

        string[] BOS = { "_B-1", "_B-2", "_B-3", "_B-4" };
        string[] EOS = { "_B+1", "_B+2", "_B+3", "_B+4" };

        string get_index(ref string p, int pos, ref int i, ref TaggerImpl tagger)
        {
            if (p[i] != '[')
            {
                return null;
            }
            i++;

            int col = 0;
            int row = 0;
            int neg = 1;

            if (p[i] == '-')
            {
                neg = -1;
                i++;
            }

            while (i < p.Length)
            {
                if (p[i] >= '0' && p[i] <= '9')
                {
                    row = 10 * row + (p[i] - '0');
                }
                else if (p[i] == ',')
                {
                    i++;
                    goto NEXT1;
                }
                else return null;

                i++;
            }

        NEXT1:
            while (i < p.Length)
            {
                if (p[i] >= '0' && p[i] <= '9')
                {
                    col = 10 * col + (p[i] - '0');
                }
                else if (p[i] == ']')
                {
                    goto NEXT2;
                }
                else return null;

                i++;
            }
        NEXT2:
            row *= neg;

            if (col < 0 || col >= xsize_)
            {
                return null;
            }
            int idx = (int)(pos + row);
            if (idx < 0)
            {
                return BOS[-idx - 1];
            }
            if (idx >= tagger.xsize_)
            {
                return EOS[idx - tagger.xsize_];
            }

            return tagger.x_[idx, col];
        }

        public int getID(string key)
        {
            if (dic_.ContainsKey(key) == false)
            {
                dic_.Add(key, new FeatureIdPair(maxid_, 1));

                int n = maxid_;
                maxid_ += (key[0] == 'U' ? y_.Count : y_.Count * y_.Count);
                return n;
            }
            else
            {
                dic_[key].Value++;
                return dic_[key].Key;
            }

        }


        public bool buildFeatures(TaggerImpl tagger)
        {
            StringBuilder os = new StringBuilder();
            List<int> feature = new List<int>();

            tagger.feature_id_ = feature_cache_.Count;
            for (int cur = 0; cur < tagger.xsize_; ++cur)
            {
                foreach (string it in unigram_templs_)
                {
                    if (apply_rule(ref os, it, cur, ref tagger) == false)
                    {
                        Console.WriteLine(" format error: " + it);
                    }

                    int id = getID(os.ToString());
                    if (id != -1)
                    {
                        feature.Add(id);
                    }
                }
                feature_cache_.Add(feature.ToArray());
                feature.Clear();
            }

            for (int cur = 1; cur < tagger.xsize_; ++cur)
            {

                foreach (string it in bigram_templs_)
                {
                    if (apply_rule(ref os, it, cur, ref tagger) == false)
                    {
                        Console.WriteLine(" format error: " + it);
                    }
                    int id = getID(os.ToString());
                    if (id != -1)
                    {
                        feature.Add(id);
                    }
                }

                feature_cache_.Add(feature.ToArray());
                feature.Clear();

            }

            return true;
        }


        bool apply_rule(ref StringBuilder feature_function, string p, int pos, ref TaggerImpl tagger)
        {
            feature_function.Clear();
            string r;

            for (int i = 0; i < p.Length; i++)
            {
                if (p[i] == '%')
                {
                    i++;
                    switch (p[i])
                    {
                        case 'x':
                            i++;
                            r = get_index(ref p, pos, ref i, ref tagger);
                            if (r == null)
                            {
                                return false;
                            }
                            feature_function.Append(r);
                            break;
                        default:
                            return false;
                    }
                }
                else
                {
                    feature_function.Append(p[i]);
                }
            }

            return true;

        }


        public void calcCost(ref Path p)
        {
            float c = 0.0f;
            int[] f = feature_cache_[p.fid];
            int offset = p.lnode_y * y_.Count + p.rnode_y + 1;
            for (int i = 0; i < f.Length; i++)
            {
                c += (alpha_)[f[i] + offset];
            }
            p.cost = cost_factor_ * c;
        }

        public void rebuildFeatures(TaggerImpl tagger)
        {
            int fid = tagger.feature_id_;
            int path_count = 0;

            for (int cur = 0; cur < tagger.xsize_; ++cur)
            {
                for (int i = 0; i < y_.Count; ++i)
                {
                    tagger.set_node(cur, i, fid);
                }
                fid++;
            }

            for (short cur = 1; cur < tagger.xsize_; ++cur)
            {
                for (short j = 0; j < y_.Count; ++j)
                {
                    for (short i = 0; i < y_.Count; ++i)
                    {
                        tagger.path_cache_[path_count].lnode_x = (short)(cur - 1);
                        tagger.path_cache_[path_count].lnode_y = j;
                        tagger.path_cache_[path_count].rnode_x = cur;
                        tagger.path_cache_[path_count].rnode_y = i;
                        tagger.path_cache_[path_count].fid = fid;
                        tagger.path_cache_[path_count].cost = 0.0f;

                        tagger.node_[cur - 1, j].rpath_id[tagger.node_[cur - 1, j].rpath_cnt] = path_count;
                        tagger.node_[cur - 1, j].rpath_cnt++;
                        tagger.node_[cur, i].lpath_id[tagger.node_[cur, i].lpath_cnt] = path_count;
                        tagger.node_[cur, i].lpath_cnt++;

                        path_count++;
                    }
                }
                fid++;
            }
        }
    }
}
